Skip to content

Exercises

Art Store, Revisited

Let's get some practice! We'll update the “art store” example we saw earlier in this module to use useReducer.

Acceptance Criteria:

  • The cartItems state should use the useReducer hook.
  • You should use the conventional switch/case structure discussed in the previous lesson within your reducer.

Code Playground

import React from 'react';

import Shop from './Shop';
import Header from './Header';
import CartButton from './CartButton';

function App() {
const [cartItems, setCartItems] = React.useState(
[]
);

function addToCart(item) {
setCartItems([...cartItems, item]);
}

return (
<>
<Header
actions={
<CartButton
numOfItems={cartItems.length}
/>
}
/>

<Shop
paintings={DATA}
addToCart={addToCart}
/>
</>
);
}

const DATA = [
{
id: 'summer-jubilee',
title: 'Summer Jubilee',
caption: 'Oil on canvas, 80" × 64"',
src: '/img/painting-01.jpg',
price: 12000,
},
{
id: 'spectacular-end',
title: 'A Spectacular End',
caption: 'Oil on canvas, 40" × 32"',
src: '/img/painting-02.jpg',
price: 4000,
},
{
id: 'crossing-the-chasm',
title: 'Crossing The Chasm',
caption: 'Oil on canvas, 32" × 24"',
src: '/img/painting-03.jpg',
price: 3600,
},
{
id: 'underneath',
title: 'Underneath',
caption: 'Oil on canvas, 40" × 32"',
src: '/img/painting-04.jpg',
price: 3000,
},
{
id: 'it-is-what-it-is',
title: 'It Is What It Is',

Solution:

Counter 2.0, One Last Time

I promise, this is the last time we'll hack on this counter example in this course!

Update the code below so that it uses the useReducer hook for the count state.

Acceptance Criteria:

  • The count state should use the useReducer hook.
  • You should use the conventional switch/case structure discussed in the previous lesson within your reducer.
  • Each button should get its own action type.

Code Playground

import React from 'react';
import {
ChevronUp,
ChevronsUp,
ChevronDown,
ChevronsDown,
RotateCcw,
Hash,
} from 'react-feather';

function Counter({ initialVal = 0 }) {
const [
count,
setCount,
] = React.useState(initialVal);

return (
<div className="wrapper">
<p>
<span>Current value:</span>
<span className="count">
{count}
</span>
</p>
<div className="button-row">
<button
onClick={() =>
setCount(count + 1)
}
>
<ChevronUp />
<span className="visually-hidden">
Increase slightly
</span>
</button>
<button
onClick={() =>
setCount(count + 10)
}
>
<ChevronsUp />
<span className="visually-hidden">
Increase a lot
</span>
</button>
<button
onClick={() =>
setCount(initialVal)
}
>
<RotateCcw />
<span className="visually-hidden">
Reset
</span>
</button>
<button
onClick={() => {
const nextCount = clamp(
Math.ceil(
Math.random() * 100
),
1,
100
);
setCount(nextCount);

Solution:

Gradient Generator, Revisited

Let's tackle one more example: the “Gradient Generator” tool from Module 2.

This one is a bit trickier, because there are two state variables. Your goal is to merge them into a single reducer.

Acceptance Criteria:

  • All state should be managed with the useReducer hook.
  • In order for this to work, the state will need to be stored as an object that contains both the colors array and the numOfVisibleColors number.

This is a difficult challenge — I haven't yet shown you exactly how to solve these sorts of problems. Unless you have previous experience with Redux, you might not be able to solve it, and that's OK! The goal here is to spend 15 minutes giving it your best shot.

I'll explain everything in the solution video below. ❤️

Code Playground

import React from 'react';

function App() {
const [colors, setColors] = React.useState([
'#FFD500',
'#FF0040',
'#FF0040',
'#FF0040',
'#FF0040',
]);
const [
numOfVisibleColors,
setNumOfVisibleColors,
] = React.useState(2);

const visibleColors = colors.slice(0, numOfVisibleColors);

const colorStops = visibleColors.join(', ');
const backgroundImage = `linear-gradient(${colorStops})`;

function addColor() {
if (numOfVisibleColors >= 5) {
window.alert('There is a maximum of 5 colors');
return;
}

setNumOfVisibleColors(numOfVisibleColors + 1);
}

function removeColor() {
if (numOfVisibleColors <= 2) {
window.alert('There is a minimum of 2 colors');
return;
}

setNumOfVisibleColors(numOfVisibleColors - 1);
}

return (
<div className="wrapper">
<div className="actions">
<button onClick={removeColor}>Remove color</button>
<button onClick={addColor}>Add color</button>
</div>

<div
className="gradient-preview"
style={{
backgroundImage,
}}
/>

<div className="colors">
{visibleColors.map((color, index) => {
const colorId = `color-${index}`;
return (
<div key={colorId} className="color-wrapper">
<label htmlFor={colorId}>
Color {index + 1}:
</label>
<div className="input-wrapper">
<input
id={colorId}
type="color"

Solution: